/*
 * Part of the ACID Project
 * Developed in 2011 by Morgan Taschuk
 */
package acid.plugin.expression.similarity;

import java.util.Collection;
import java.util.Iterator;

/**
 *
 * @author morgan
 */
public class CalculatePairwiseSimilarity
{

    /**
     * Calculates the Euclidean similarity between two vectors
     * @param u
     * @param v
     * @return
     * @throws NumberFormatException
     */
    public static double calculateEuclid(double[] u, double[] v)
            throws NumberFormatException
    {
        double result = 0;

        double d = u.length;
        if (d != v.length)
        {
            throw new NumberFormatException("Vectors must be of the same length");
        }

        double sum = 0;
        for (int i = 0; i < d; i++)
        {
            sum += Math.pow(v[i] - u[i], 2);
        }
        result = Math.sqrt((1 / d) * sum);

        return result;
    }

    /**
     * Calculates the Pearson correlation between two vectors.
     * @param u
     * @param v
     * @return
     */
    public static double calculatePearson(double[] u, double[] v)
    {
        double result = 0;
        double d = u.length;
        if (d != v.length)
        {
            throw new NumberFormatException("Vectors must be of the same length");
        }

        double sum = 0;
        double stdDevU = calculateStdDev(u);
        double meanU = calculateMean(u);
        double stdDevV = calculateStdDev(v);
        double meanV = calculateMean(v);
        for (int i = 0; i < d; i++)
        {
            double part1 = v[i] - meanV;
            double part2 = u[i] - meanU;
            double part3 = stdDevV * stdDevU;
            sum += (part1 * part2) / (part3);
        }

        result = (1 / d) * sum;

        return result;
    }

    /**
     * Calculates the arithmetic mean.
     * @param arr the array to calculate the mean of
     * @return the mean
     */
    public static double calculateMean(double[] arr)
    {
        double result = 0;
        for (int i = 0; i < arr.length; i++)
        {
            result += arr[i];
        }
        return result / arr.length;
    }

    /**
     * Calculates the sample standard deviation with
     * Bessel's correction.
     * @param arr the array to calculate the standard deviation of
     * @return the standard deviation of the given array
     */
    public static double calculateStdDev(double[] arr)
    {
        double result = 0;

        double n = arr.length;

        double sum = 0;
        double meanArr = calculateMean(arr);
        for (int i = 0; i < n; i++)
        {
            sum += Math.pow(arr[i] - meanArr, 2);
        }

        result = Math.sqrt((1 / (n - 1)) * sum);

        return result;
    }

    /**
     * Calculates the EucPear measure, which is a combination of Euclidean and
     * Pearson z-score measures. Yona et al. 2006 found this measure to be the
     * best overall for similarity measures of microarray expression data.
     *
     * @param u vector of expression levels for gene U
     * @param v vector of expression levels for gene V
     * @return the measure of distance between the vectors
     */
    public static double calculateEucPear(double[] u, double[] v)
    {

        double result = 0;

        result = calculateZScorePearson(u, v) - calculateZScoreEuclid(u, v);

        return result;
    }

    /**
     * Calculates the Z-score using Pearson measure.
     *
     * @param u vector of expression levels for gene U
     * @param v vector of expression levels for gene V
     * @return the measure of distance between the vectors
     */
    public static double calculateZScorePearson(double[] uv, double[] vv)
    {

        double trueScore = calculatePearson(uv, vv);
        PermuteVector pearsonPermute = new PermuteVector(vv)
        {

            @Override
            protected double calculateDistance(double[] u, double[] v)
            {
                return calculatePearson(u, v);
            }
        };
        return calculatePermutedDistances(uv, trueScore, pearsonPermute);

    }

    /**
     * Calculates the Z-score using Euclidean distances.
     *
     * @param u vector of expression levels for gene U
     * @param v vector of expression levels for gene V
     * @return the measure of distance between the vectors
     */
    public static double calculateZScoreEuclid(double[] uv, double[] vv)
    {

        double trueScore = calculateEuclid(uv, vv);

        PermuteVector euclidPermute = new PermuteVector(vv)
        {

            @Override
            protected double calculateDistance(double[] u, double[] v)
            {
                return calculateEuclid(u, v);
            }
        };
        return calculatePermutedDistances(uv, trueScore, euclidPermute);
    }

    /**
     * Calculates the distance between the u vector and the v vector that is used
     * in initialising the calculationType.
     *
     * @param u vector of expression levels for gene U
     * @param trueScore the true distance between the two vectors
     * @param calculationType an instance of PermuteVector with a particular
     * type of calculation implemented (i.e. Euclidean)
     * @return the measure of distance between the vectors
     */
    private static double calculatePermutedDistances(double[] u,
            double trueScore, PermuteVector calculationType)
    {
        double result = 0;


        calculationType.permute(u);
        double[] distances = calculationType.getDistances();
        double mean = calculateMean(distances);
        double stdDev = calculateStdDev(distances);

        result = (trueScore - mean) / stdDev;

        return result;
    }

    public static double[] toDoubleArray(Collection<Double> exp)
    {
        double[] arr = new double[exp.size()];
        Iterator<Double> iterator = exp.iterator();
        for (int i = 0; i < arr.length; i++)
        {
            arr[i] = iterator.next();
        }
        return arr;
    }
}
